gatsby-source-notion-churnotion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # plugin
2
+
3
+ The main focus of the overall tutorial. This contains the whole source plugin.
@@ -0,0 +1,9 @@
1
+ import { NotionToMarkdown } from "notion-to-md";
2
+ /**
3
+ * Page to Markdown Instance
4
+ */
5
+ export declare const n2m: NotionToMarkdown;
6
+ /**
7
+ * Notion API Instance
8
+ */
9
+ export declare const instance: import("axios").AxiosInstance;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.instance = exports.n2m = void 0;
7
+ const client_1 = require("@notionhq/client");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const notion_to_md_1 = require("notion-to-md");
10
+ const constants_1 = require("../constants");
11
+ const notion = new client_1.Client({
12
+ auth: process.env.GATSBY_INTEGRATION_TOKEN,
13
+ });
14
+ /**
15
+ * Page to Markdown Instance
16
+ */
17
+ exports.n2m = new notion_to_md_1.NotionToMarkdown({ notionClient: notion });
18
+ /**
19
+ * Notion API Instance
20
+ */
21
+ exports.instance = axios_1.default.create({
22
+ baseURL: `https://api.notion.com/v1/`,
23
+ headers: {
24
+ "Content-Type": `application/json`,
25
+ "Notion-Version": constants_1.NOTION_API_VERSION,
26
+ Authorization: `Bearer ${process.env.GATSBY_INTEGRATION_TOKEN}`,
27
+ },
28
+ });
@@ -0,0 +1,2 @@
1
+ import { IGetPagesParams } from "../types";
2
+ export declare const getPages: ({ databaseId, reporter, getCache, actions, createNode, createNodeId, createParentChildLink, getNode, }: IGetPagesParams) => Promise<void>;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPages = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const constants_1 = require("../constants");
9
+ const fetchData_1 = require("../util/fetchData");
10
+ const imageProcessor_1 = require("../util/imageProcessor");
11
+ const slugify_1 = require("../util/slugify");
12
+ const connector_1 = require("./connector");
13
+ const getPages = async ({ databaseId, reporter, getCache, actions, createNode, createNodeId, createParentChildLink, getNode, }) => {
14
+ let hasMore = true;
15
+ /**
16
+ * 데이터베이스 내에 페이지들을 읽어서 재귀적으로 추가하는 서브 메서드드
17
+ * @param databaseId 데이터베이스 아이디
18
+ * @param parentCategoryId 부모 데이터베이스 아이디
19
+ */
20
+ const processDatabase = async (databaseId, parentCategoryId = null) => {
21
+ try {
22
+ while (hasMore) {
23
+ const databaseUrl = `databases/${databaseId}/query`;
24
+ const body = {};
25
+ const result = await (0, fetchData_1.fetchPostWithRetry)(databaseUrl, body);
26
+ if (result?.results?.length) {
27
+ reporter.info(`[SUCCESS] total pages > ${result.results.length}`);
28
+ }
29
+ for (const page of result.results) {
30
+ reporter.info(`[CHECK!!!] page: ${page.id}`);
31
+ const pageUrl = `blocks/${page.id}/children?page_size=100`;
32
+ // 페이지 데이터
33
+ const pageData = await (0, fetchData_1.fetchGetWithRetry)(pageUrl);
34
+ if (pageData.results[0].type === `child_database`) {
35
+ const categoryJsonData = pageData.results[0];
36
+ const title = categoryJsonData.child_database?.title || `Unnamed Category`;
37
+ const slug = (0, slugify_1.slugify)(title);
38
+ if (!title) {
39
+ reporter.warn(`[WARNING] Category without a title detected: ${categoryJsonData.id}`);
40
+ }
41
+ const nodeId = createNodeId(`${categoryJsonData.id}-category`);
42
+ const categoryNode = {
43
+ id: nodeId,
44
+ category_name: title,
45
+ parent_id: parentCategoryId,
46
+ slug: slug || `no-title-${categoryJsonData.id}`,
47
+ children: [],
48
+ internal: {
49
+ type: constants_1.NODE_TYPE.Category,
50
+ contentDigest: crypto_1.default
51
+ .createHash(`md5`)
52
+ .update(JSON.stringify(categoryJsonData))
53
+ .digest(`hex`),
54
+ },
55
+ };
56
+ createNode(categoryNode);
57
+ if (parentCategoryId && categoryNode) {
58
+ const parentNode = getNode(parentCategoryId); // Gatsby에서 노드를 검색
59
+ if (parentNode) {
60
+ createParentChildLink({
61
+ parent: parentNode,
62
+ child: categoryNode,
63
+ });
64
+ reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${categoryNode.category_name}`);
65
+ }
66
+ else {
67
+ reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
68
+ }
69
+ }
70
+ await processDatabase(categoryJsonData.id, nodeId);
71
+ }
72
+ else {
73
+ // 페이지인 경우
74
+ const title = page.properties?.[`이름`]?.title?.[0]?.plain_text || `Unnamed`;
75
+ const slug = (0, slugify_1.slugify)(title);
76
+ if (!title) {
77
+ reporter.warn(`[WARNING] Category without a title detected: ${page.id}`);
78
+ }
79
+ const nodeId = createNodeId(`${page.id}-page`);
80
+ // Tag 노드 만들기
81
+ if (page.properties.tags && page.properties.tags.multi_select) {
82
+ page.properties.tags.multi_select.map((tagData) => createNode({
83
+ id: createNodeId(`${tagData.id}-tag`),
84
+ tag_name: tagData.name,
85
+ color: tagData.color,
86
+ internal: {
87
+ type: constants_1.NODE_TYPE.Tag,
88
+ contentDigest: crypto_1.default
89
+ .createHash(`md5`)
90
+ .update(JSON.stringify(tagData.id))
91
+ .digest(`hex`),
92
+ },
93
+ }));
94
+ }
95
+ const bookId = page.properties?.book?.relation?.[0]?.id || null;
96
+ const markdownContent = await connector_1.n2m.pageToMarkdown(page.id);
97
+ await (0, imageProcessor_1.processBlocks)(markdownContent, actions, getCache, createNodeId, reporter);
98
+ const postNode = {
99
+ id: nodeId,
100
+ category_id: parentCategoryId,
101
+ book_id: bookId,
102
+ title: title,
103
+ content: markdownContent,
104
+ create_date: page.created_time,
105
+ update_date: page.last_edited_time,
106
+ version: page.properties?.version?.rich_text?.[0]?.plain_text || null,
107
+ description: null,
108
+ slug: slug || `no-title-${nodeId}`,
109
+ children: [],
110
+ internal: {
111
+ type: constants_1.NODE_TYPE.Post,
112
+ contentDigest: crypto_1.default
113
+ .createHash(`md5`)
114
+ .update(JSON.stringify(nodeId))
115
+ .digest(`hex`),
116
+ },
117
+ };
118
+ await createNode(postNode);
119
+ if (parentCategoryId && postNode) {
120
+ const parentNode = getNode(parentCategoryId); // Gatsby에서 노드를 검색
121
+ if (parentNode) {
122
+ createParentChildLink({
123
+ parent: parentNode,
124
+ child: postNode,
125
+ });
126
+ reporter.info(`[SUCCESS] Linked parent: ${parentNode.category_name} -> child: ${postNode.title}`);
127
+ }
128
+ else {
129
+ reporter.warn(`[WARNING] Parent node not found for ID: ${parentCategoryId}`);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ hasMore = result.has_more;
135
+ }
136
+ }
137
+ catch (error) {
138
+ reporter.error(`[ERROR] fetching page`);
139
+ }
140
+ };
141
+ await processDatabase(databaseId);
142
+ };
143
+ exports.getPages = getPages;
@@ -0,0 +1,9 @@
1
+ export declare const NOTION_API_VERSION = "2022-06-28";
2
+ export declare const NOTION_LIMIT_ERROR = 429;
3
+ export declare const PLUGIN_NAME = "gatsby-source-notion-churnotion";
4
+ export declare const NODE_TYPE: {
5
+ Post: string;
6
+ Category: string;
7
+ Tag: string;
8
+ Book: string;
9
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NODE_TYPE = exports.PLUGIN_NAME = exports.NOTION_LIMIT_ERROR = exports.NOTION_API_VERSION = void 0;
4
+ exports.NOTION_API_VERSION = `2022-06-28`;
5
+ exports.NOTION_LIMIT_ERROR = 429;
6
+ exports.PLUGIN_NAME = `gatsby-source-notion-churnotion`;
7
+ //////////////////////////////////////////////////
8
+ exports.NODE_TYPE = {
9
+ Post: `Churnotion`,
10
+ Category: `NCategory`,
11
+ Tag: `NTag`,
12
+ Book: `NBook`,
13
+ };
@@ -0,0 +1,2 @@
1
+ import { GatsbyNode } from "gatsby";
2
+ export declare const createSchemaCustomization: GatsbyNode[`createSchemaCustomization`];
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSchemaCustomization = void 0;
4
+ const constants_1 = require("./constants");
5
+ const createSchemaCustomization = ({ actions }) => {
6
+ const { createTypes } = actions;
7
+ createTypes(`
8
+ type ${constants_1.NODE_TYPE.Post} implements Node {
9
+ id: ID!
10
+ category_id: ${constants_1.NODE_TYPE.Category} @link(by: "id")
11
+ tags: ${constants_1.NODE_TYPE.Tag} @link(by: "id")
12
+ book_id: ${constants_1.NODE_TYPE.Book} @link(by: "id")
13
+ title: String
14
+ content: [JSON]
15
+ create_date: Date! @dateformat
16
+ update_date: Date! @dateformat
17
+ version: Int
18
+ description: String
19
+ slug: String
20
+ }
21
+
22
+ type ${constants_1.NODE_TYPE.Tag} implements Node {
23
+ id: ID!
24
+ tag_name: String!
25
+ color: String!
26
+ }
27
+
28
+ type ${constants_1.NODE_TYPE.Category} implements Node {
29
+ id: ID!
30
+ parent_id: ${constants_1.NODE_TYPE.Category} @link(by: "id")
31
+ category_name: String!
32
+ slug: String!
33
+ }
34
+
35
+ type ${constants_1.NODE_TYPE.Book} implements Node {
36
+ id: ID!
37
+ book_name: String!
38
+ create_date: Date! @dateformat
39
+ update_date: Date! @dateformat
40
+ }
41
+ `);
42
+ };
43
+ exports.createSchemaCustomization = createSchemaCustomization;
@@ -0,0 +1,4 @@
1
+ export type { IPluginOptions } from "./types";
2
+ export { onPluginInit } from "./on-plugin-init";
3
+ export { sourceNodes } from "./source-nodes";
4
+ export { createSchemaCustomization } from "./createSchemaCustomization";
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSchemaCustomization = exports.sourceNodes = exports.onPluginInit = void 0;
4
+ var on_plugin_init_1 = require("./on-plugin-init");
5
+ Object.defineProperty(exports, "onPluginInit", { enumerable: true, get: function () { return on_plugin_init_1.onPluginInit; } });
6
+ var source_nodes_1 = require("./source-nodes");
7
+ Object.defineProperty(exports, "sourceNodes", { enumerable: true, get: function () { return source_nodes_1.sourceNodes; } });
8
+ var createSchemaCustomization_1 = require("./createSchemaCustomization");
9
+ Object.defineProperty(exports, "createSchemaCustomization", { enumerable: true, get: function () { return createSchemaCustomization_1.createSchemaCustomization; } });
@@ -0,0 +1,2 @@
1
+ import type { GatsbyNode } from "gatsby";
2
+ export declare const onPluginInit: GatsbyNode[`onPluginInit`];
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.onPluginInit = void 0;
4
+ const onPluginInit = ({ reporter }) => {
5
+ reporter.info(`Example plugin loaded...`);
6
+ };
7
+ exports.onPluginInit = onPluginInit;
@@ -0,0 +1,2 @@
1
+ import type { GatsbyNode } from "gatsby";
2
+ export declare const sourceNodes: GatsbyNode["sourceNodes"];
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sourceNodes = void 0;
4
+ const getPage_1 = require("./api/getPage");
5
+ const sourceNodes = async (gatsbyApi, options) => {
6
+ const { actions, reporter, createNodeId, getNode, getCache } = gatsbyApi;
7
+ const { createNode, createParentChildLink } = actions;
8
+ const { token, databaseId } = options;
9
+ if (!token || !databaseId) {
10
+ reporter.error(`[ERROR] Missing Notion API token or database ID.`);
11
+ return;
12
+ }
13
+ reporter.info(`[INFO] Fetching pages from Notion database...`);
14
+ try {
15
+ await (0, getPage_1.getPages)({
16
+ token,
17
+ databaseId,
18
+ reporter,
19
+ getCache,
20
+ actions,
21
+ createNode,
22
+ createNodeId,
23
+ createParentChildLink,
24
+ getNode,
25
+ });
26
+ }
27
+ catch (e) {
28
+ reporter.error(`[ERROR] ${e}`);
29
+ }
30
+ };
31
+ exports.sourceNodes = sourceNodes;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * POST request with retry logic using axios instance
3
+ * @param {string} url - API endpoint URL
4
+ * @param {any} body - Request body
5
+ * @param {number} tryCount - Current retry count
6
+ * @param {number} maxRetries - Maximum retry attempts
7
+ * @returns {Promise<any>}
8
+ */
9
+ export declare const fetchPostWithRetry: (url: string, body: any, tryCount?: number, maxRetries?: number) => Promise<any>;
10
+ /**
11
+ * General GET/PUT/DELETE request with retry logic using axios instance
12
+ * @param {string} url - API endpoint URL
13
+ * @param {any} options - Axios request options
14
+ * @param {number} tryCount - Current retry count
15
+ * @param {number} maxRetries - Maximum retry attempts
16
+ * @returns {Promise<any>}
17
+ */
18
+ export declare const fetchGetWithRetry: (url: string, options?: any, tryCount?: number, maxRetries?: number) => Promise<any>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchGetWithRetry = exports.fetchPostWithRetry = void 0;
4
+ const connector_1 = require("../api/connector");
5
+ const constants_1 = require("../constants");
6
+ const timeLimit_1 = require("./timeLimit");
7
+ /**
8
+ * POST request with retry logic using axios instance
9
+ * @param {string} url - API endpoint URL
10
+ * @param {any} body - Request body
11
+ * @param {number} tryCount - Current retry count
12
+ * @param {number} maxRetries - Maximum retry attempts
13
+ * @returns {Promise<any>}
14
+ */
15
+ const fetchPostWithRetry = async (url, body, tryCount = 0, maxRetries = 5) => {
16
+ try {
17
+ const response = await connector_1.instance.post(url, body);
18
+ // Return data if the request was successful
19
+ return response.data;
20
+ }
21
+ catch (error) {
22
+ const status = error.response?.status;
23
+ const message = error.response?.data?.message || `Unknown error`;
24
+ // Handle rate limit errors with exponential backoff
25
+ if (status === constants_1.NOTION_LIMIT_ERROR && tryCount < maxRetries) {
26
+ const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff: 1s, 2s, 4s...
27
+ console.log(`[${constants_1.PLUGIN_NAME}] Rate limit hit. Retrying in ${delay / 1000}s...`);
28
+ await (0, timeLimit_1.sleep)(delay);
29
+ return (0, exports.fetchPostWithRetry)(url, body, tryCount + 1, maxRetries);
30
+ }
31
+ // Handle unexpected errors with retry
32
+ if (tryCount < maxRetries) {
33
+ const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff
34
+ console.log(`[${constants_1.PLUGIN_NAME}] Unexpected error. Retrying in ${delay / 1000}s...`);
35
+ await (0, timeLimit_1.sleep)(delay);
36
+ return (0, exports.fetchPostWithRetry)(url, body, tryCount + 1, maxRetries);
37
+ }
38
+ // Log and throw the error if all retries fail
39
+ console.error(`[${constants_1.PLUGIN_NAME}] Failed after ${tryCount} retries:`, message);
40
+ throw error;
41
+ }
42
+ };
43
+ exports.fetchPostWithRetry = fetchPostWithRetry;
44
+ /**
45
+ * General GET/PUT/DELETE request with retry logic using axios instance
46
+ * @param {string} url - API endpoint URL
47
+ * @param {any} options - Axios request options
48
+ * @param {number} tryCount - Current retry count
49
+ * @param {number} maxRetries - Maximum retry attempts
50
+ * @returns {Promise<any>}
51
+ */
52
+ const fetchGetWithRetry = async (url, options = {}, tryCount = 0, maxRetries = 5) => {
53
+ try {
54
+ const response = await connector_1.instance.get(url);
55
+ // Return data if the request was successful
56
+ return response.data;
57
+ }
58
+ catch (error) {
59
+ const status = error.response?.status;
60
+ const message = error.response?.data?.message || `Unknown error`;
61
+ // Handle rate limit errors with exponential backoff
62
+ if (status === constants_1.NOTION_LIMIT_ERROR && tryCount < maxRetries) {
63
+ const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff: 1s, 2s, 4s...
64
+ console.log(`[${constants_1.PLUGIN_NAME}] Rate limit hit. Retrying in ${delay / 1000}s...`);
65
+ await (0, timeLimit_1.sleep)(delay);
66
+ return (0, exports.fetchGetWithRetry)(url, options, tryCount + 1, maxRetries);
67
+ }
68
+ // Handle unexpected errors with retry
69
+ if (tryCount < maxRetries) {
70
+ const delay = Math.pow(2, tryCount) * 1000; // Exponential backoff
71
+ console.log(`[${constants_1.PLUGIN_NAME}] Unexpected error. Retrying in ${delay / 1000}s...`);
72
+ await (0, timeLimit_1.sleep)(delay);
73
+ return (0, exports.fetchGetWithRetry)(url, options, tryCount + 1, maxRetries);
74
+ }
75
+ // Log and throw the error if all retries fail
76
+ console.error(`[${constants_1.PLUGIN_NAME}] Failed after ${tryCount} retries:`, message);
77
+ throw error;
78
+ }
79
+ };
80
+ exports.fetchGetWithRetry = fetchGetWithRetry;
@@ -0,0 +1,3 @@
1
+ import { Actions, GatsbyCache, Reporter } from "gatsby";
2
+ import { MdBlock } from "notion-to-md/build/types";
3
+ export declare const processBlocks: (blocks: MdBlock[], actions: Actions, getCache: (this: void, id: string) => GatsbyCache, createNodeId: (this: void, input: string) => string, reporter: Reporter) => Promise<void>;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.processBlocks = void 0;
7
+ const gatsby_source_filesystem_1 = require("gatsby-source-filesystem");
8
+ const path_1 = __importDefault(require("path"));
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const processBlocks = async (blocks, actions, getCache, createNodeId, reporter) => {
11
+ const { createNode } = actions;
12
+ for (const block of blocks) {
13
+ // 이미지 블록 처리
14
+ if (block.type === `image` &&
15
+ typeof block.parent === `string` &&
16
+ block.parent.includes(`http`)) {
17
+ const match = block.parent.match(/\((https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp))\)/);
18
+ if (match) {
19
+ const imageUrl = match[1]; // 이미지 URL 추출
20
+ try {
21
+ // 해싱된 파일명 생성
22
+ const fileExtension = path_1.default.extname(imageUrl); // 확장자 추출
23
+ const hashedFileName = crypto_1.default.createHash(`md5`).update(imageUrl).digest(`hex`) +
24
+ fileExtension;
25
+ // 파일 생성
26
+ const fileNode = await (0, gatsby_source_filesystem_1.createRemoteFileNode)({
27
+ url: imageUrl,
28
+ parentNodeId: block.blockId, // 블록 ID를 부모로 설정
29
+ getCache,
30
+ createNode,
31
+ createNodeId,
32
+ name: hashedFileName,
33
+ });
34
+ if (fileNode) {
35
+ const relativePath = `/static/images/${hashedFileName}`; // 해싱된 파일명을 사용
36
+ block.parent = `![${hashedFileName}](${relativePath})`; // Markdown 형식으로 업데이트
37
+ reporter.info(`[SUCCESS] Updated block with new hashed image path: ${block.parent}`);
38
+ }
39
+ }
40
+ catch (error) {
41
+ reporter.warn(`[WARNING] Failed to download image: ${imageUrl}`);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ };
47
+ exports.processBlocks = processBlocks;
@@ -0,0 +1 @@
1
+ export declare const slugify: (text: string) => string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.slugify = void 0;
4
+ const slugify = (text) => {
5
+ return text.toLowerCase().replace(/\s+/g, `-`);
6
+ };
7
+ exports.slugify = slugify;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Sleep for a given number of milliseconds
3
+ * @param {number} ms - Milliseconds to sleep
4
+ */
5
+ export declare const sleep: (ms: number) => Promise<unknown>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleep = void 0;
4
+ /**
5
+ * Sleep for a given number of milliseconds
6
+ * @param {number} ms - Milliseconds to sleep
7
+ */
8
+ const sleep = (ms) => {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ };
11
+ exports.sleep = sleep;
package/gatsby-node.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require(`./dist/gatsby-node`);
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "gatsby-source-notion-churnotion",
3
+ "description": "Gatsby plugin that can connect with One Notion Database RECURSIVELY using official API",
4
+ "version": "1.0.0",
5
+ "skipLibCheck": true,
6
+ "license": "0BSD",
7
+ "main": "./dist/gatsby-node.js",
8
+ "files": [
9
+ "./dist/*",
10
+ "gatsby-node.js"
11
+ ],
12
+ "scripts": {
13
+ "clean": "del-cli dist",
14
+ "build": "tsc",
15
+ "develop": "tsc --watch",
16
+ "test": "jest",
17
+ "prepare": "npm run clean && npm run build"
18
+ },
19
+ "keywords": [
20
+ "gatsby",
21
+ "gatsby-plugin",
22
+ "notion",
23
+ "gatsby-notion",
24
+ "recursive"
25
+ ],
26
+ "author": "Churnobyl <tjdcjfals3@naver.com> (https://github.com/Churnobyl)",
27
+ "homepage": "https://github.com/Churnobyl/gatsby-source-notion-churnotion",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Churnobyl/gatsby-source-notion-churnotion"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "dependencies": {
36
+ "@notionhq/client": "^2.2.15",
37
+ "@types/node": "^22.10.2",
38
+ "axios": "^1.7.9",
39
+ "gatsby-source-filesystem": "^5.14.0",
40
+ "gatsby-transformer-json": "^5.14.0",
41
+ "node-fetch": "^3.3.2",
42
+ "notion-to-md": "^3.1.1",
43
+ "typescript": "^5.7.2"
44
+ },
45
+ "devDependencies": {
46
+ "gatsby": "^5.14.0"
47
+ },
48
+ "peerDependencies": {
49
+ "gatsby": "^5.14.0",
50
+ "react": "^18.0.0",
51
+ "react-dom": "^18.0.0"
52
+ }
53
+ }